using System;
using System.IO;
using System.Net;
using System.Web;
using System.Xml;
using System.Text;
using System.Threading;
using System.Collections.Generic;
using PickGold.Util;

namespace PickGold.Web
{
	public class CPostFileInfo : CUpDownFile
	{
		protected XmlDocument _XML = null;
		//
		public CPostFileInfo()
		{
			this._XML = new XmlDocument();
			this._XML.AppendChild(this._XML.CreateElement(this.GetType().Name));
		}
		public CPostFileInfo(string t)
		{
			if (File.Exists(t))
				this.OpenXml(t);
		}
		public CPostFileInfo(HttpContext context, string path)
		{
			this.Get(context, path);
		}

		protected bool OpenXml()
		{
			string cp = this.CachePath;
			if (string.IsNullOrEmpty(cp))
				return false;

			string eg = this.Etag;
			if (string.IsNullOrEmpty(eg))
				return false;

			return this.OpenXml(cp + '/' + eg + ".pfi");
		}
		protected bool OpenXml(string path)
		{
			if (!File.Exists(path))
			{
				this.InnerException = new FileNotFoundException(path, path);
				return false;
			}

			for (int i = byte.MaxValue; i > 0; i--)
			{
				try
				{
					FileStream fs = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.None);
					if (this._XML == null)
						this._XML = new XmlDocument();
					this._XML.Load(fs);
					fs.Close();
					fs.Dispose();
					fs = null;
					i = 0;
					return true;
				}
				catch (Exception x)
				{
					Tracker.WriteError(x);
					this.InnerException = x;
					Thread.Sleep(0);
				}
			}

			return false;
		}

		protected bool SaveXml()
		{
			string cp = this.CachePath;
			if (string.IsNullOrEmpty(cp))
				return false;

			string eg = this.Etag;
			if (string.IsNullOrEmpty(eg))
				return false;

			return this.SaveXml(cp + '/' + eg + ".pfi");
		}
		protected bool SaveXml(string path)
		{
			for (int i = byte.MaxValue; i > 0; i--)
			{
				try
				{
					FileStream fs = new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.None);
					if (this._XML == null)
						this._XML = new XmlDocument();
					this._XML.Save(fs);
					fs.Close();
					fs.Dispose();
					fs = null;
					i = 0;
					return true;
				}
				catch (Exception x)
				{
					Tracker.WriteError(x);
					this.InnerException = x;
					Thread.Sleep(0);
				}
			}

			return false;
		}

		public override string Etag
		{
			set
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.ETAG] == null)
				{
					this._XML.DocumentElement.Attributes.Append(this._XML.CreateAttribute(HttpHeaderKey.ETAG)).Value = value;
					return;
				}

				this._XML.DocumentElement.Attributes[HttpHeaderKey.ETAG].Value = value;
			}
			get
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.ETAG] == null)
					return null;

				return this._XML.DocumentElement.Attributes[HttpHeaderKey.ETAG].Value;
			}
		}

		public string FileName
		{
			set
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.FILE_NAME] == null)
				{
					this._XML.DocumentElement.Attributes.Append(this._XML.CreateAttribute(HttpHeaderKey.FILE_NAME)).Value = value;
					this.SaveXml();
					return;
				}

				this._XML.DocumentElement.Attributes[HttpHeaderKey.FILE_NAME].Value = value;
				this.SaveXml();
			}
			get
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.FILE_NAME] == null)
					return null;

				return this._XML.DocumentElement.Attributes[HttpHeaderKey.FILE_NAME].Value;
			}
		}

		public string CachePath
		{
			set
			{
				if (this._XML.DocumentElement.Attributes[this.GetType().Name] == null)
				{
					this._XML.DocumentElement.Attributes.Append(this._XML.CreateAttribute(this.GetType().Name)).Value = value;
					this.SaveXml();
					return;
				}

				this._XML.DocumentElement.Attributes[this.GetType().Name].Value = value;
				this.SaveXml();
			}
			get
			{
				if (this._XML.DocumentElement.Attributes[this.GetType().Name] == null)
					return null;

				return this._XML.DocumentElement.Attributes[this.GetType().Name].Value;
			}
		}

		public override long ContentLength
		{
			set
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.CONTENT_LENGTH] == null)
				{
					this._XML.DocumentElement.Attributes.Append(this._XML.CreateAttribute(HttpHeaderKey.CONTENT_LENGTH)).Value = value.ToString();
					this.SaveXml();
					return;
				}

				this._XML.DocumentElement.Attributes[HttpHeaderKey.CONTENT_LENGTH].Value = value.ToString();
				this.SaveXml();
			}
			get
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.CONTENT_LENGTH] == null)
					return 0;

				return long.Parse(this._XML.DocumentElement.Attributes[HttpHeaderKey.CONTENT_LENGTH].Value);
			}
		}

		public override DateTime LastModified
		{
			set
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.LAST_MODIFIED] == null)
				{
					this._XML.DocumentElement.Attributes.Append(this._XML.CreateAttribute(HttpHeaderKey.LAST_MODIFIED)).Value = value.ToString();
					this.SaveXml();
					return;
				}

				this._XML.DocumentElement.Attributes[HttpHeaderKey.LAST_MODIFIED].Value = value.ToString();
				this.SaveXml();
			}
			get
			{
				if (this._XML.DocumentElement.Attributes[HttpHeaderKey.LAST_MODIFIED] == null)
					return DateTime.MinValue;

				return DateTime.Parse(this._XML.DocumentElement.Attributes[HttpHeaderKey.LAST_MODIFIED].Value);
			}
		}

		public void AddPart()
		{
			this.AddPart(this._Start, this._End);
		}
		public void AddPart(long start, long end)
		{
			if (start >= end)
				return;

			if (!this.OpenXml())
				return;

			XmlNode nod = this._XML.DocumentElement.AppendChild(this._XML.CreateElement(HttpHeaderKey.CONTENT_RANGE));
			nod.Attributes.Append(this._XML.CreateAttribute(START)).Value = start.ToString();
			nod.Attributes.Append(this._XML.CreateAttribute(END)).Value = end.ToString();
			this.SaveXml();
		}

		public int PartCount
		{
			get
			{
				if (!this.OpenXml())
					return -1;

				return this._XML.DocumentElement.ChildNodes.Count;
			}
		}

		public long GetPartStart(int i)
		{
			return long.Parse(this._XML.DocumentElement.ChildNodes[i].Attributes[START].Value);
		}

		public long GetPartEnd(int i)
		{
			return long.Parse(this._XML.DocumentElement.ChildNodes[i].Attributes[END].Value);
		}

		public int FindPartStart(long start, long end)
		{
			long l;
			for (int i = 0; i < this._XML.DocumentElement.ChildNodes.Count; i++)
			{
				l = long.Parse(this._XML.DocumentElement.ChildNodes[i].Attributes[START].Value);
				if (l >= start && l <= end)
					return i;
			}

			return -1;
		}

		public int FindPartEnd(long start, long end)
		{
			long l;
			for (int i = 0; i < this._XML.DocumentElement.ChildNodes.Count; i++)
			{
				l = long.Parse(this._XML.DocumentElement.ChildNodes[i].Attributes[END].Value);
				if (l >= start && l <= end)
					return i;
			}

			return -1;
		}

		public void Save()
		{
			this.Save(this.CachePath + '/' + this.Etag + ".pfi");
		}
		public void Save(string path)
		{
			this.SaveXml(path);
		}

		public long GetCompletedLength()
		{
			XmlNode nod;
			long ls, le;
			long start = 0, end = 0;
			if (!this.OpenXml())
				return -1;

			long len = this.ContentLength;
			for (int i = 0; i < this._XML.DocumentElement.ChildNodes.Count; i++)
			{
				nod = this._XML.DocumentElement.ChildNodes[i];
				ls = long.Parse(nod.Attributes[START].Value);
				le = long.Parse(nod.Attributes[END].Value);
				if (ls >= start && ls <= end + 1 && le > end)
				{
					start = ls;
					end = le;
					i = -1;
				}
			}

			return end;
		}

		public long MergerTo(string path)
		{
			if (this.GetCompletedLength() < this.ContentLength - 1)
				return -1;

			return this.MergerTo(new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Delete));
		}

		public long MergerTo(string path, bool overWrite)
		{
			if (this.GetCompletedLength() < this.ContentLength - 1)
				return -1;

			long l;
			if (overWrite)
				l = this.MergerTo(new FileStream(path, FileMode.Create, FileAccess.Write, FileShare.Delete));
			else
				l = this.MergerTo(new FileStream(path, FileMode.CreateNew, FileAccess.Write, FileShare.Delete));
			File.SetLastWriteTime(path, this.LastModified);
			return l;
		}
		public long MergerTo(Stream str)
		{
			int i, k;
			string t;
			XmlNode nod;
			FileStream fs;
			long lS = 0, lE = 0, ls = 0, le = 0;
			byte[] bs = new byte[BitUtil.K];
			if (!this.OpenXml())
				return -1;

			string path = this.CachePath + '/' + this.Etag;

			for (i = 0; i < this._XML.DocumentElement.ChildNodes.Count; i++)
			{
				nod = this._XML.DocumentElement.ChildNodes[i];
				ls = long.Parse(nod.Attributes[START].Value);
				le = long.Parse(nod.Attributes[END].Value);
				if (ls >= lS && ls <= lE + 1 && le > lE)
				{
					t = string.Format("{0}/{1}-{2}.part", path, ls, le);
					if (!File.Exists(t))
						return lE;

					if (str.Position != ls)
						str.Seek(ls, 0);
					fs = new FileStream(t, FileMode.Open, FileAccess.Read, FileShare.Read);
					while (true)
					{
						k = fs.Read(bs, 0, bs.Length);
						if (k <= 0)
							break;
						str.Write(bs, 0, k);
						str.Flush();
					}
					fs.Close();
					fs.Dispose();
					fs = null;

					lS = ls;
					lE = le;
					i = -1;
				}
			}
			str.Close();

			return lE;
		}

		public void Delete()
		{
			string t;
			XmlNode nod;
			if (!this.OpenXml())
				return;

			string path = this.CachePath + '/' + this.Etag;
			for (int i = 0; i < this._XML.DocumentElement.ChildNodes.Count; i++)
			{
				nod = this._XML.DocumentElement.ChildNodes[i];
				t = string.Format("{0}/{1}-{2}.part", path, nod.Attributes[START].Value, nod.Attributes[END].Value);
				if (File.Exists(t))
					File.Delete(t);
			}

			if (Directory.Exists(path))
				Directory.Delete(path);

			File.Delete(path + ".pfi");
		}

		public long Post(string url, long size)
		{
			WebRequest wr = HttpWebRequest.Create(url);
			long l = this.Post(wr, size);
			try
			{
				wr.GetResponse().Close();
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
			}
			return l;
		}
		public long Post(Uri uri, long size)
		{
			WebRequest wr = HttpWebRequest.Create(uri);
			long l = this.Post(wr, size);
			try
			{
				wr.GetResponse().Close();
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
			}
			return l;
		}
		public long Post(WebRequest request, long size)
		{
			if (size <= 0)
				return this.Post(request);

			this._Start = -1;
			this._End = -1;
			FileInfo fio = new FileInfo(this.FileName);
			if (size >= fio.Length)
				return this.Post(request);

			WebRequest wr = HttpWebRequest.Create(request.RequestUri);
			if (string.IsNullOrEmpty(this.Etag))
			{
				FileStream fs = new FileStream(fio.FullName, FileMode.Open, FileAccess.Read);
				this.Etag = BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(fs));
				fs.Close();
				fs.Dispose();
				fs = null;
			}
			wr.ContentType = HttpHeaderKey.CONTENT_TYPE_BREAK_POINT;
			wr.Headers.Add(string.Format("{0}:{1}", HttpHeaderKey.ETAG, this.Etag));
			wr.Headers.Add(string.Format("{0}:{1}", HttpHeaderKey.FILE_NAME, HttpUtility.UrlEncode(fio.FullName)));
			wr.Headers.Add(string.Format("{0}:{0:R}", HttpHeaderKey.LAST_MODIFIED, fio.LastWriteTime));
			int i = 0;
			long lr = 0;
			long[][] ls;
			try
			{
				XmlDocument xml = new XmlDocument();
				xml.LoadXml(new StreamReader(wr.GetResponse().GetResponseStream()).ReadToEnd());
				ls = new long[xml.DocumentElement.ChildNodes.Count][];
				for (i = 0; i < ls.Length; i++)
				{
					ls[i] = new long[2];
					ls[i][0] = CParser.ConvertTo<long>(xml.DocumentElement.ChildNodes[i].Attributes[START].Value);
					ls[i][1] = CParser.ConvertTo<long>(xml.DocumentElement.ChildNodes[i].Attributes[END].Value);
				}
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
				ls = new long[][] { new long[] { -1, -1 } };
			}

			if (this._Start < 0)
				this._Start = 0;
			for (long l = this._Start; l < fio.Length; l += size)
			{
				for (i = 0; i < ls.Length; i++)
				{
					if (ls[i][0] <= l && ls[i][1] >= l + size - 1)
						l += size;
				}

				if (l >= fio.Length - 1)
					break;

				this._Start = l;
				this._End = l + size - 1;
				if (this._End > fio.Length)
					this._End = fio.Length - 1;
				wr = HttpWebRequest.Create(request.RequestUri);
				for (i = 0; i < request.Headers.Count; i++)
					wr.Headers.Add(request.Headers[i]);
				lr += this.Post(wr);
				try
				{
					wr.GetResponse().Close();
				}
				catch (Exception x)
				{
					Tracker.WriteError(x);
					this.InnerException = x;
					break;
				}
			}
			return lr;
		}
		public long Post(string url)
		{
			WebRequest wr = HttpWebRequest.Create(url);
			long l = this.Post(wr);
			try
			{
				wr.GetResponse().Close();
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
			}
			return l;
		}
		public long Post(Uri uri)
		{
			var wr = HttpWebRequest.Create(uri);
			var l = this.Post(wr);
			try
			{
				wr.GetResponse().Close();
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
			}
			return l;
		}
		public long Post(WebRequest request)
		{
			var fio = new FileInfo(this.FileName);
			if (!fio.Exists)
				return 0;

			if (this.SetRange(this._Start, this._End, fio.Length) <= 0)
				return 0;

			var bs = new byte[BitUtil.K];
			var fs = new FileStream(fio.FullName, FileMode.Open, FileAccess.Read);
			if (string.IsNullOrEmpty(this.Etag))
				this.Etag = BitConverter.ToString(new System.Security.Cryptography.MD5CryptoServiceProvider().ComputeHash(fs));
			request.Method = "POST";
			request.ContentLength = this._End - this._Start + 1;
			request.ContentType = HttpHeaderKey.CONTENT_TYPE_BREAK_POINT;
			request.Headers.Add(string.Format("{0}:{1}", HttpHeaderKey.ETAG, this.Etag));
			request.Headers.Add(string.Format("{0}:{1}", HttpHeaderKey.FILE_NAME, HttpUtility.UrlEncode(fio.FullName)));
			request.Headers.Add(string.Format("{0}:{0:R}", HttpHeaderKey.LAST_MODIFIED, fio.LastWriteTime));
			request.Headers.Add(string.Format(HttpHeaderKey.CONTENT_RANGE + ":bytes{0}-{1}/{2}", this._Start, this._End, fio.Length));
			if (fs.Position != this._Start)
				fs.Seek(this._Start, 0);
			var i = 0;//new StreamReader(HttpWebRequest.Create(request.RequestUri.ToString()).GetResponse().GetResponseStream()).ReadToEnd()
			var str = request.GetRequestStream();
			this._Current = 0;
			while (true)
			{
				i = fs.Read(bs, 0, bs.Length);
				if (i <= 0)
					break;
				try
				{
					if (this._Current + i > this._End)
						str.Write(bs, 0, CParser.ConvertTo<int>(this._End - this._Current + 1));
					else
						str.Write(bs, 0, i);
				}
				catch (Exception x)
				{
					Tracker.WriteError(x);
					this.InnerException = x;
					break;
				}
				str.Flush();
				this._Current += i;
				this.OnPostBytes(new EventArgs());
			}
			try
			{
				str.Close();
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
			}
			return this._Current;
		}

		public long Get(HttpContext context, string path)
		{
			if (string.IsNullOrEmpty(path))
				return -1;

			if (!CPostFileInfo.IsBreakPoint(context.Request))
				return -1;

			string t = context.Request.ServerVariables[HttpHeaderKey.HTTP_ETAG];
			if (string.IsNullOrEmpty(t))
				return -1;

			path = path.Replace('\\', '/');
			if (path[path.Length - 1] != '/')
				path = path + '/' + t;
			else
				path = path + t;
			if (this._XML == null)
				this._XML = new XmlDocument();
			if (File.Exists(path + ".pfi"))
			{
				this.OpenXml(path + ".pfi");
			}
			else
			{
				this._XML.AppendChild(this._XML.CreateElement(this.GetType().Name));
				this.Etag = t;
				this.CachePath = path.Substring(0, path.LastIndexOf('/'));
				this.FileName = context.Request.FilePath;
				DateTime dt;
				if (DateTime.TryParse(context.Request.Headers[HttpHeaderKey.LAST_MODIFIED], out dt))
					this.LastModified = dt;
				else
					this.LastModified = DateTime.Now;
				//this.SaveXml(path + ".pfi");
			}

			t = context.Request.ServerVariables[HttpHeaderKey.CONTENT_RANGE];
			if (string.IsNullOrEmpty(t))
			{
				context.Response.ClearContent();
				context.Response.ContentType = "text/xml";
				context.Response.Write(this);
				context.Response.Flush();
				context.Response.End();
				this.Dispose();
				return 0;
			}

			if (this.SetRange(t) <= 0)
				return -1;

			t = string.Format("{0}/{1}-{2}.part", path, this._Start, this._End);
			if (File.Exists(t))
				return 0;

			if (!Directory.Exists(path))
				Directory.CreateDirectory(path);

			t = path + string.Format("/{0}-{1}.{2}", this._Start, this._End, new Random().Next());
			var fio = new FileInfo(t);
			var fs = new FileStream(fio.FullName, FileMode.OpenOrCreate, FileAccess.Write, FileShare.ReadWrite);
			var bs = new byte[BitUtil.K];
			this._Current = 0;
			try
			{
				while (true)
				{
					var i = context.Request.InputStream.Read(bs, 0, BitUtil.K);
					if (i <= 0)
						break;
					fs.Write(bs, 0, i);
					fs.Flush();
					this._Current += i;
					this.OnGetBytes(new EventArgs());
				}
			}
			catch (Exception x)
			{
				Tracker.WriteError(x);
				this.InnerException = x;
				this._End = this._Start + this._Current - 1;
			}
			fs.Close();
			fio.Refresh();
			if (fio.Length == 0)
				fio.Delete();
			else
				fio.MoveTo(string.Format("{0}/{1}-{2}.part", path, this._Start, this._End));

			this.AddPart();
			this.Save();
			return this._Current;
		}

		public override string ToString()
		{
			if (!this.OpenXml())
				return null;

			return this._XML.OuterXml;
		}

		public static bool IsBreakPoint(HttpRequest request)
		{
			string t = request.ContentType;
			if (string.IsNullOrEmpty(t))
				return false;

			if (t.Trim().ToLower() == HttpHeaderKey.CONTENT_TYPE_BREAK_POINT)
				return true;

			return false;
		}
		//
	}
	//
}
//
